# -*- coding: utf-8 -*-
"""
Created on Tue Oct 11 11:32:39 2022

@author: Administrator
"""
import copy
import queue, pymysql
import requests
import pandas as pd
import numpy as np
from kds_util.user_logbook import user_log, system_log
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, Any
from kds_util.user_logbook import user_log as logger
import traceback
from base.DirsFile import ConFigDB
from util import io_klines


# %% 计算个票排序参数
class GetRankData:
    def __init__(self, code):
        '''
        RKdata 数据格式
         {'投机度_5增量': -0.021572137575494, '投机度_12增量': -0.002313978655775838, '投机度_21增量': 0.001725470187820685, '重叠度_5增量': -0.14345272463077235,
            '重叠度_12增量': -0.05318871947587135, '重叠度_21增量': -0.02758767367783784, '持仓量_5增量': -110444.4480252078, '持仓量_12增量': -49572.43138832516,
            '持仓量_21增量': -26007.71286121196, '偏离度_5增量': -1.1505747309440582, '偏离度_12增量': -0.47920761045795374, '偏离度_21增量': -0.25995003561625246,
            '波动率_5增量': -0.009992251198386854, '波动率_12增量': -0.02229204807791468, '波动率_21增量': -0.024108595584488867, '涨跌幅_5增量': -1.0877543908185354, 
            '涨跌幅_12增量': -0.5086529365489789, '涨跌幅_21增量': -0.2938212467225267, '涨幅_5D': -1.6689847009735743, '涨幅_12D': 0.6166982922201139,
            '涨幅_21D': 2.3154848046309695, '涨幅_60D': 3.868756121449559, '涨幅_125D': 14.763536253590086, '偏离_5D': -1.9508136094674473, '偏离_12D': -0.8974982965054096,
            '偏离_21D': -0.09868789951777401, 'code': 'rbL9'}
        '''
        self.code = code
        self.RKdata = None
        # ==================================================================
        ###排序统计参数模块
        self.ATR = ATR()  ###波动率
        self.TJD = TJD()  ###投机度
        self.NDInCrP = NDInCrP()  ###涨跌幅
        self.Overlap = Overlap()  ###重叠度
        self.BiasN = [BIAS() for i in range(3)]  ###偏离度
        self.DuaTonN = [DuaTon() for i in range(6)]  ###增量性
        # ==================================================================

    def input(self, kline, REFkline):
        # ==================================================================
        ###排序模块日线级别输入↓
        偏离周期 = [5, 12, 21]
        self.NDInCrP.input(kline.close)
        self.Overlap.input(kline, REFkline)
        self.TJD.input(kline.volume, kline.oi)
        self.ATR.input(kline, promotion=1, M0=20, arge=20, N=2)
        for i in range(3): self.BiasN[i].input(kline.close, 偏离周期[i])
        # ==================================================================
        self.DuaTonN[0].input(self.TJD.tjd)  # 投机度增量
        self.DuaTonN[1].input(self.Overlap.olp)  # 重叠度增量
        self.DuaTonN[2].input(kline.oi)  # 持仓量增量
        self.DuaTonN[3].input(self.BiasN[0].bias)  # 偏离度增量
        self.DuaTonN[4].input(100 * self.ATR.atr / kline.close)  # 波动率增量
        self.DuaTonN[5].input(InCrP(kline.close, REFkline.close))  # 涨跌幅增量
        self.RKdata = self.RankData()  # 排序数据收集

        # ==================================================================

    def RankData(self):  ###排序指标反馈模块{自排序,全局排序}
        try:
            持续增量 = ["投机度", "重叠度", "持仓量", "偏离度", "波动率", "涨跌幅"]
            data, 增量周期, 偏离周期, 涨幅周期 = {"code": self.code}, [5, 12, 21], [5, 12, 21], [5, 12, 21, 60, 125]
            for i in range(6):
                for j in range(3): data.update({"%s_%d增量" % (持续增量[i], 增量周期[j]): self.DuaTonN[i].dtups[j]})
            for i in range(5): data.update({"涨幅_%dD" % 涨幅周期[i]: self.NDInCrP.InCrPN[i]})
            for i in range(3): data.update({"偏离_%dD" % 偏离周期[i]: self.BiasN[i].bias})
            return data
        except Exception as exception:
            user_log.error(str(exception))
        # ==================================================================


# %% 排序选股
def XGBRank(train, label, test):
    import xgboost as xgb
    from xgboost import DMatrix
    train_data = DMatrix(train, label)
    xgb_rank_params = {
        'objective': 'rank:pairwise',
        'gamma': 0.1,
        'min_child_weight': 1.1,
        'max_depth': 6,
        'lambda': 12,
        'subsample': 0.7,
        'colsample_bytree': 0.7,
        'colsample_bylevel': 0.7,
        'eta': 0.01,
        'tree_method': 'exact',
        'seed': 0,
        'nthread': 12,
        'eval_metric': 'ndcg'}
    rankModel = xgb.train(xgb_rank_params, train_data)
    test_data = DMatrix(test)
    scores = rankModel.predict(test_data)
    return scores


###############################################################################
# %% 排序选股模块
class RankModel:
    '''初始化'''

    def __init__(self):
        self.df, self.data = pd.DataFrame(), pd.DataFrame({"code": []})

    def ToRank(self, BKRDG):
        '''
        输入数据格式
        {'SAL9': {'投机度_5增量': -0.021572137575494, '投机度_12增量': -0.002313978655775838, '投机度_21增量': 0.001725470187820685, '重叠度_5增量': -0.14345272463077235,
                  '重叠度_12增量': -0.05318871947587135, '重叠度_21增量': -0.02758767367783784, '持仓量_5增量': -110444.4480252078, '持仓量_12增量': -49572.43138832516,
                  '持仓量_21增量': -26007.71286121196, '偏离度_5增量': -1.1505747309440582, '偏离度_12增量': -0.47920761045795374, '偏离度_21增量': -0.25995003561625246,
                  '波动率_5增量': -0.009992251198386854, '波动率_12增量': -0.02229204807791468, '波动率_21增量': -0.024108595584488867, '涨跌幅_5增量': -1.0877543908185354, 
                  '涨跌幅_12增量': -0.5086529365489789, '涨跌幅_21增量': -0.2938212467225267, '涨幅_5D': -1.6689847009735743, '涨幅_12D': 0.6166982922201139,
                  '涨幅_21D': 2.3154848046309695, '涨幅_60D': 3.868756121449559, '涨幅_125D': 14.763536253590086, '偏离_5D': -1.9508136094674473, '偏离_12D': -0.8974982965054096,
                  '偏离_21D': -0.09868789951777401, 'code': 'rbL9'}, 
         'MAL9': {'投机度_5增量': -0.021572137575494, '投机度_12增量': -0.002313978655775838,'投机度_21增量': 0.001725470187820685, '重叠度_5增量': -0.14345272463077235,
                  '重叠度_12增量': -0.05318871947587135, '重叠度_21增量': -0.02758767367783784, '持仓量_5增量': -110444.4480252078, '持仓量_12增量': -49572.43138832516,
                  '持仓量_21增量': -26007.71286121196, '偏离度_5增量': -1.1505747309440582, '偏离度_12增量': -0.47920761045795374, '偏离度_21增量': -0.25995003561625246,
                  '波动率_5增量': -0.009992251198386854, '波动率_12增量': -0.02229204807791468, '波动率_21增量': -0.024108595584488867, '涨跌幅_5增量': -1.0877543908185354,
                  '涨跌幅_12增量': -0.5086529365489789, '涨跌幅_21增量': -0.2938212467225267, '涨幅_5D': -1.6689847009735743, '涨幅_12D': 0.6166982922201139,
                  '涨幅_21D': 2.3154848046309695, '涨幅_60D': 3.868756121449559, '涨幅_125D': 14.763536253590086, '偏离_5D': -1.9508136094674473, '偏离_12D': -0.8974982965054096, 
                  '偏离_21D': -0.09868789951777401, 'code': 'rbL9'}, 
         'rbL9': {'投机度_5增量': -0.021572137575494, '投机度_12增量': -0.002313978655775838,'投机度_21增量': 0.001725470187820685, '重叠度_5增量': -0.14345272463077235,
                  '重叠度_12增量': -0.05318871947587135, '重叠度_21增量': -0.02758767367783784, '持仓量_5增量': -110444.4480252078, '持仓量_12增量': -49572.43138832516,
                  '持仓量_21增量': -26007.71286121196, '偏离度_5增量': -1.1505747309440582, '偏离度_12增量': -0.47920761045795374, '偏离度_21增量': -0.25995003561625246,
                  '波动率_5增量': -0.009992251198386854, '波动率_12增量': -0.02229204807791468, '波动率_21增量': -0.024108595584488867, '涨跌幅_5增量': -1.0877543908185354, 
                  '涨跌幅_12增量': -0.5086529365489789, '涨跌幅_21增量': -0.2938212467225267, '涨幅_5D': -1.6689847009735743, '涨幅_12D': 0.6166982922201139,
                  '涨幅_21D': 2.3154848046309695, '涨幅_60D': 3.868756121449559, '涨幅_125D': 14.763536253590086, '偏离_5D': -1.9508136094674473, '偏离_12D': -0.8974982965054096,
                  '偏离_21D': -0.09868789951777401, 'code': 'rbL9'}}
        '''
        df = pd.DataFrame(list(BKRDG.values()))
        if df.empty: return {"Rank": []}
        df.index = df["code"]
        for col in df.columns: df[col] = pd.to_numeric(df[col], errors='ignore')
        df.sort_values(by='涨幅_5D', axis=0, ascending=False, inplace=True)  # 涨幅降序#保证金率*倍数*vol
        df["label"] = [(i + 1) for i in range(len(df))]  ###标签设置 # abs((i+1)-len(df)//2)
        if not self.df.empty:
            self.df["label"] = df["label"]
            scores = self.XGBRank(self.df.iloc[:, 1:-1], self.df["label"], df.iloc[:, 1:-1])
            self.data = copy.deepcopy(df)
            self.data["scores"] = scores
            self.data.sort_values(by='scores', axis=0, ascending=True, inplace=True)  # 涨幅降序
            rst = {"Rank": self.data["code"].to_list()}
        else:
            rst = {"Rank": []}
        self.df = copy.deepcopy(df)
        return rst  # []

    def XGBRank(self, train, label, test):
        '''机器学习模型'''
        import xgboost as xgb
        from xgboost import DMatrix
        train_data = DMatrix(train, label)
        xgb_rank_params = {
            'objective': 'rank:pairwise',
            'gamma': 0.1,
            'min_child_weight': 1.1,
            'max_depth': 5,
            'lambda': 10,
            'subsample': 0.7,
            'colsample_bytree': 0.7,
            'colsample_bylevel': 0.7,
            'eta': 0.01,
            'tree_method': 'exact',
            'seed': 0,
            'nthread': 12,
            'eval_metric': 'ndcg'}
        rankModel = xgb.train(xgb_rank_params, train_data)
        test_data = DMatrix(test)
        scores = rankModel.predict(test_data)
        return scores


class DuaTon:
    '''持续增量'''

    def __init__(self, mark=0):
        self.mark = mark
        self.dtup, self.DTMA, self.dtupN, self.dtups = 0, MA(), [EMA() for i in range(3)], [0, ] * 3

    def input(self, oi, arges=[5, 12, 21], N=3, promotion=1):
        self.dtup = oi - self.DTMA.ma
        self.DTMA.input(oi, N * promotion)
        for i in range(3): self.dtupN[i].input(self.dtup, arges[i], promotion)
        self.dtups = [self.dtupN[i].ema for i in range(3)]


# //////////////////////////////////////////////////////////////////////////////
class Overlap:
    '''K线重叠度'''

    def __init__(self, mark=0):
        self.mark = mark
        self.olp = 0  # 重叠度
        self.hlp = 0  # 高低比

    def input(self, NK, RK):
        HH, LL = max(NK.high, RK.high), min(NK.low, RK.low)
        LH, HL = min(NK.high, RK.high), max(NK.low, RK.low)
        MHL = ((NK.high - NK.low) + (RK.high - RK.low))
        self.hlp = abs(LH - HL) / abs(HH - LL) if abs(HH - LL) != 0 else 0
        self.olp = (LH - HL) / (MHL / 2) if (LH > HL) & (MHL != 0) else 0


# //////////////////////////////////////////////////////////////////////////////
def InCrP(NC, RC):
    '''涨幅'''
    return 100 * (NC - RC) / RC if RC != 0 else 0


class NDInCrP:
    '''N日涨跌幅'''

    def __init__(self, mark=0):
        self.mark = mark
        self.InCrPN, self.PrC = [0, ] * 5, [0, ] * 1000

    def input(self, price, arges=[5, 12, 21, 60, 125]):
        self.PrC.append(price)
        self.PrC = self.PrC[-2 * (max(arges)):]
        self.InCrPN = [InCrP(price, self.PrC[-i]) for i in arges]


# //////////////////////////////////////////////////////////////////////////////
class TJD:  #
    '''投机度'''

    def __init__(self, mark=0):
        self.mark = mark
        self.MAV, self.MAI, self.Tjd = MA(), MA(), 1

    def input(self, VOL, OI, promotion=1, arge=5):
        self.MAV.input(VOL, arge, promotion)
        self.MAI.input(OI, arge, promotion)
        self.Tjd = self.MAV.ma / self.MAI.ma if self.MAI.ma != 0 else 0
        self.tjd = VOL / OI if OI != 0 else 0


def QX_Data(breed: str = "all", TDday: str = str(datetime.now())[:10], SQL: str = "False",
            ipport: str = "127.0.0.1:5555"):
    '''期现数据表API'''
    try:
        import requests, json
        url = f"http://{ipport}/api/qh/qx_data/"
        data = {"breed": breed,
                "Date": TDday,
                "SQL": SQL}
        headers = {'content-type': 'application/json;charset=utf-8'}  # , 'Connection': 'keep-alive'
        rst = requests.post(url, json.dumps(data), headers=headers)
        return EVALI(rst.text)["data"]
    except:
        return []


def QXJX(code, trade_date, num=1.6):
    '''期现货基差'''
    code = delint(code)
    data = QX_Data(code.upper(), trade_date, ipport="dataapi.tocmcc.cn:5000")
    if len(data) < 1: return False
    return abs(data[0]['主力合约_现期差%']) < num


# //////////////////////////////////////////////////////////////////////////////
def Acconts_all(url='http://127.0.0.1:8088/api/mgr/allaccounts'):
    '''
    获取所以账户信息
    注:保存的数据是str 转回只需eval()
    '''
    if not hasattr(Acconts_all, 'allacconts'):
        headers = {'content-type': 'application/json;charset=utf-8', 'Connection': 'keep-alive'}
        try:
            r = requests.request('GET', url, headers=headers)
        except:
            r = requests.request('GET', 'http://192.168.1.101:8088/api/mgr/allaccounts', headers=headers)
        Acconts_all.allacconts = r.json()["result"]
    allacconts = Acconts_all.allacconts
    return allacconts


def Account(account):
    '''
    获取指定账户信息
    注:保存的数据是str 转回只需eval()
    '''
    headers = {'content-type': 'application/json;charset=utf-8', 'Connection': 'keep-alive'}
    url = Acconts_all()[account]['url'] + "/oneaccountinfo?investor_id=" + account
    r = requests.request('GET', url, headers=headers)
    result = r.json()["result"]
    result['CodesUP'] = [x.upper() for x in result['Codes']]
    return result


# //////////////////////////////////////////////////////////////////////////////
###数据库中取账户信息
class ACCGet:
    def __init__(self):
        ''' 
        初始化数据并更新到数据库REDIS
        '''
        self.account = Acconts_all()
        self.ADDCFG("account", self.account)
        self.keys = list(self.account.keys())

    def GetACC(self, key):
        if key not in self.keys: key = "moni186"
        data, Fee = Account(key), {}
        for i in range(len(data["Codes"])):
            Fee[data["Codes"][i]] = self.GetFee(data["Contracts"][i], key)
        data["Fee"] = Fee
        self.ADDCFG(key, data)
        return data

    def ADDCFG(self, section, jsons, PATH="./ConFigs/AccData/"):
        CFG = ConFigDB(PATH)
        CFG.Add_section(section, jsons)
        CFG.Save(filename=section)

    def RedACC(self, filename=None, PATH="./ConFigs/AccData/"):
        if filename not in self.keys: filename = "moni186"
        CFG = ConFigDB(PATH)
        CFG.Read(filename)
        acc = CFG.GetDict(filename)
        # acc={Data[i][0]:EVALI(Data[i][1]) for i in range(len(Data))}
        return acc

    def GetFee(self, Contract, monixxx):
        if monixxx not in self.keys: monixxx = "moni186"
        url, investor_id = self.account[monixxx]["url"], self.account[monixxx]["investor_id"]
        headers = {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'}
        url = "%s%s?investor_id=%s&code=%s" % (url, '/accountcode', investor_id, Contract)
        r = requests.request('GET', url, headers=headers)
        return r.json()["result"]


##########################################################################################
def MainCode(df, code):
    '''返回合约对应主力合约'''
    try:
        maincode = df[df["main_id"] == f"{delint(code)}L9"]["instrument_id"].iloc[0]
        return maincode
    except:
        pass


def InitMainCode():
    '''初始化获取主力合约列表数据'''
    configsql = {'host': '127.0.0.1', 'user': 'root',
                 'password': '2234567', 'dbname': 'hqdb',
                 'tblname': '', 'charset': 'utf8'}
    lcdb = MySqlDB(configsql)  # 创建本地数据库连接
    df = lcdb.GetData("SELECT * from tbl_future_main_contract ", rstdf=True, Multiple=True)
    return df


##########################################################################################
def TradeRecodeN(lcdb, code, price, Num, OCLS, priceupdw=2):
    '''
    config={'host':'192.168.1.186','user':'root','password':'123456',
            'dbname':None,'tblname':None,'charset':'utf8'}
    lcdb=MySqlDB(config)#创建数据库连接
    code合约,price参考价格,Num多少天内的交易记录,OCLS开平仓标志,priceupdw=2浮动2个点
    '''
    lcdb.UseDB("hqdb")
    TradeDateTime = (datetime.now() - timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')
    string = f"SELECT * FROM `tbl_future_trade` where TradeDateTime>={TradeDateTime} ;"
    df = lcdb.GetData(string, rstdf=True)
    if len(df) > 0:
        data = df[(df["InstrumentID"] == code) & (df["OffsetFlag"] == OCLS)]
        if len(data) > 0:
            if (price + priceupdw) <= data["price"] <= (price + priceupdw): return True
    return False


##########################################################################################
# %%半日K
def HalfKline(Dkline, mkline, Refmkline):
    '''是否触发半根K线
        Dkline日K线
        mkline一分钟当前K线
        Refmkline上一分钟当前K线
    '''
    hc = (Dkline.high + Dkline.low) / 2
    mx = abs((Dkline.high - Dkline.low) * 0.1)
    hcUP = hc + mx
    hcDW = hc - mx
    cond0 = (mkline.high > hc and mkline.low < hc) or (Refmkline.high < hc and mkline.close > hc) or (
            Refmkline.low > hc and mkline.close < hc)
    cond1 = (mkline.high > hcUP and mkline.low < hcUP) or (Refmkline.high < hcUP and mkline.close > hcUP) or (
            Refmkline.low > hcUP and mkline.close < hcUP)
    cond2 = (mkline.high > hcDW and mkline.low < hcDW) or (Refmkline.high < hcDW and mkline.close > hcDW) or (
            Refmkline.low > hcDW and mkline.close < hcDW)
    return True if cond0 or cond1 or cond2 else False


# %%N日限制信号
class LossN5Day:
    def __init__(self):
        self.REFls = 0
        self.lsnum = 1

    def Dayinput(self, ls):
        '''
        每日结束时输入多空信号
        ls 基础条件多空信号【一定得是基础条件信号】
        '''
        LS = (ls / abs(ls))
        if self.REFls != LS:
            self.lsnum = 1
        else:
            self.lsnum += 1
        self.REFls = LS

    def input(self, ls, DayMa5, kline, NUM=5):
        '''
        逻辑说明 ：NUM(5)日内多空信号发生了切换则跌破5日线止损
        
        ls 基础条件多空信号【一定得是基础条件信号】
        DayMa5 日线Ma5
        kline  分钟K线
        return : True需要止损False不需要止损
        '''
        if self.lsnum > NUM:
            return False
        else:
            if ls > 0:  # 多信号
                if kline.low < DayMa5: return True
            elif ls < 0:  # 空信号
                if kline.high > DayMa5: return True
            else:
                return False


##########################################################################################
class CCLBSCD:
    '''
    持仓量多空过滤 
    '''

    def __init__(self):
        self.CCL = CCL()
        self.BCCL = []
        self.SCCL = []
        self.UPDW = 0

    def Dayinput(self, DayKline, num=20):
        '''
        DayKline 日K线
        '''
        price, oi = DayKline.close, DayKline.oi
        self.CCL.input(price, oi)
        self.SCCL.append(oi) if self.CCL.result == '空头增仓' else self.SCCL.append(0)
        self.BCCL.append(oi) if self.CCL.result == '多头增仓' else self.BCCL.append(0)
        self.BCCL = self.BCCL[-num:]
        self.SCCL = self.SCCL[-num:]
        self.UPDW = 1 if sum(self.BCCL) > sum(self.SCCL) else (-1 if sum(self.BCCL) < sum(self.SCCL) else 0)

    def input(self, ls, DayMa5, kline):
        '''
        逻辑说明 ：条件相反五日线止损
        
        ls 基础条件多空信号【一定得是基础条件信号】
        DayMa5 日线Ma5
        kline  分钟K线
        return : True需要止损False不需要止损
        '''
        if ls > 0 and self.UPDW < 0:  # 多信号
            if kline.low < DayMa5: return True
        elif ls < 0 and self.UPDW > 0:  # 空信号
            if kline.high > DayMa5: return True
        else:
            return False


def INT50100(close):
    '''整数价格判断'''
    return ((close % 50) == 0 or (close % 100) == 0)


# %%大波动
def ATRPC(klines, num=100):
    '''每日收盘前判断是否是大波动K线，如果是就强制平仓，第二天再开仓'''
    ATRx = ATR()
    for kline in klines:
        ATRx.input(kline, promotion=1, M0=num, arge=21, N=2)
    return ATRx.MTR > ATRx.atr  # 属于大波动True


# %%模糊五日线
class BSma5:
    def __init__(self):
        self.UPMA5 = 0
        self.DWMA5 = 0

    def inputD5MA(self, Daykline5, minkline, num=5):
        '''
        Daykline5日K线列表
        
        minkline最新一分钟K线
        
        '''
        self.ATRx = ATR()
        self.D5MA = MA()
        for i in range(len(Daykline5)):
            kline = Daykline5[i]
            price = kline.close
            self.D5MA.input(price)
            self.ATRx.input(kline, promotion=1, M0=120, arge=21, N=2)
            self.UPMA5 = self.D5MA.ma + self.ATRx.atr
            self.DWMA5 = self.D5MA.ma - self.ATRx.atr
        if self.UPMA5 > minkline.low > self.D5MA.ma:
            return 1
        elif self.DWMA5 < minkline.high < self.D5MA.ma:
            return -1
        else:
            return 0


# %% 五日线金死叉
class CDBASE5:
    def __init__(self):
        self.D5MA = MA()
        self.REFK = None
        self.REFC, self.REFDayMA = 0, 0
        self.initDayMA()

    def initDayMA(self):
        '''初始化日内分时线'''
        self.DayMA = MA()

    def inputDayMA(self, price, DKNUM):
        '''传入价格和日内第几根K线数量'''
        self.DayMA.input(price, DKNUM)
        INTprice = INT50100(price)  ###50 100整数关口
        JXDayMA = CROSX(price, self.DayMA.ma, self.REFC, self.REFDayMA) == 1  # 金叉
        SXDayMA = CROSX(self.DayMA.ma, price, self.REFDayMA, self.REFC) == 1  # 死叉
        self.REFC, self.REFDayMA = price, self.DayMA.ma
        ###价格上穿日内分时线###价格下穿日内分时线
        return INTprice, JXDayMA, SXDayMA

    def inputD5MA(self, kline5, num=5):
        '''计算5日线'''
        self.D5MA = MA()
        self.REFK = None
        UPXMA5, DWXMA5 = False, False
        for i in range(len(kline5)):
            kline = kline5[i]
            price = kline.close
            self.D5MA.input(price)
            CutMa5 = (kline.high > self.D5MA.ma and kline.low < self.D5MA.ma)
            if self.REFK != None and CutMa5 and i >= (num - 1):
                ###下影线碰五日线###上影线碰五日线
                UPXMA5 = (self.REFK.high < self.D5MA.ma)
                DWXMA5 = (self.REFK.low > self.D5MA.ma)
            self.REFK = copy.deepcopy(kline)
        return UPXMA5, DWXMA5


##########################################################################################

class Cond001:
    '''
    姜总顶底条件：
    HK:REF(HHV(H,20),1);
    LK:REF(LLV(L,20),1);
    EM:SMA(O,21,3);
    MK:=(HK-LK)*0.2;
    HLK:=HK-MK;
    LHK:=LK+MK;
    OC:=ABS(C-O);
    HOC:=MA(MA(OC,20),100);
    M5:=MA(C,5);

    DWCOND:=CROSS(C,LHK) AND C>O;
    DWCOND1:DWCOND OR REF(DWCOND,1),NODRAW;
    DWCOND2:(OC+REF(OC,1))>=2*MA(OC,60),NODRAW;
    DWCOND3:(OC+REF(OC,1))>HOC,NODRAW;
    DWCOND4:C>MA(C,5),NODRAW;

    UPCOND:=CROSS(HLK,C) AND C<O;
    UPCOND1:UPCOND OR REF(UPCOND,1),NODRAW;
    UPCOND2:(OC+REF(OC,1))>=2*MA(OC,60),NODRAW;
    UPCOND3:(OC+REF(OC,1))>HOC,NODRAW;
    UPCOND4:C<MA(C,5),NODRAW;

    STICKLINE(DWCOND AND DWCOND1 AND DWCOND2 AND DWCOND3 AND DWCOND4,H,L,3,0),COLORYELLOW;
    STICKLINE(UPCOND AND UPCOND1 AND UPCOND2 AND UPCOND3 AND UPCOND4,H,L,3,0)COLORBLUE;

    ZIGH:=ZIG(H,2),COLORYELLOW,LINETHICK2;
    ZIGL:=ZIG(L,2),COLORYELLOW,LINETHICK2;
    COND0:=REF(ZIGH>REF(ZIGH,1) AND ZIGH<REFX(ZIGH,1),1);
    COND1:=REF(ZIGL<REF(ZIGL,1) AND ZIGL>REFX(ZIGL,1),1);

    X0:REF(DRAWLINE(COND0,H,COND1,L,0),1),COLORYELLOW,LINETHICK2;
    X1:REF(DRAWLINE(COND1,L,COND0,H,0),1),COLORYELLOW,LINETHICK2;


    {STICKLINE(DWCOND1 AND DWCOND2 AND DWCOND3 AND DWCOND4,H,L,3,0),COLORBLUE;
    STICKLINE(UPCOND1 AND UPCOND2 AND UPCOND3 AND UPCOND4,H,L,3,0),COLORYELLOW;};
    '''

    def __init__(self):
        self.SMA = SMA()
        self.Hlists, self.Llists, self.H, self.L, self.MH, self.ML = [], [], 0, 0, 0, 0
        self.OC, self.REFOC, self.MAa, self.MAb, self.MAc, self.MAd = 0, 0, MA(), MA(), MA(), MA()
        self.REFC, self.REFMH, self.REFML = 0, 0, 0
        self.DWCOND, self.UPCOND = 0, 0
        self.UP, self.DW = 0, 0
        self.UPX, self.DWX = 0, 0
        self.lossC, self.REFlossC = 0, 0
        self.Num = 0

    def input(self, kline, Harge=20, Large=20):
        ##########################################################################################
        self.Num += 1
        self.SMA.input(kline.close, 21, 3)
        self.Hlists.append(kline.high)
        self.Llists.append(kline.low)
        if len(self.Hlists) > 10:
            self.H, self.L = max(self.Hlists[-(Harge + 1):-1]), min(self.Llists[-(Large + 1):-1])
        else:
            self.H, self.L = 0, 0
        MK = abs(self.H - self.L) * 0.2
        self.MH, self.ML = self.H - MK, self.L + MK
        ##########################################################################################
        self.OC = abs(kline.close - kline.open)
        self.MAa.input(self.OC, 20)
        self.MAb.input(self.MAa.ma, 100)
        ##########################################################################################
        self.MAc.input(kline.close, 5)
        self.MAd.input(self.OC, 60)
        ##########################################################################################
        DWCOND = CROSX(kline.close, self.ML, self.REFC, self.REFML) == 1 and kline.close > kline.open
        DWCOND1 = DWCOND or self.DWCOND
        DWCOND2 = (self.OC + self.REFOC) >= 2 * self.MAd.ma
        DWCOND3 = (self.OC + self.REFOC) > self.MAb.ma
        DWCOND4 = kline.close > self.MAc.ma
        ##########################################################################################
        UPCOND = CROSX(self.MH, kline.close, self.REFMH, self.REFC) == 1 and kline.close < kline.open
        UPCOND1 = UPCOND or self.UPCOND
        UPCOND2 = (self.OC + self.REFOC) >= 2 * self.MAd.ma
        UPCOND3 = (self.OC + self.REFOC) > self.MAb.ma
        UPCOND4 = kline.close < self.MAc.ma

        ##########################################################################################
        self.UP = 1 if UPCOND and UPCOND1 and UPCOND2 and UPCOND3 and UPCOND4 else 0
        self.DW = 1 if DWCOND and DWCOND1 and DWCOND2 and DWCOND3 and DWCOND4 else 0

        if self.UP:
            self.lossC = kline.high
            self.Num = 1
        if self.DW:
            self.lossC = kline.low
            self.Num = 1
        self.REFlossC = self.lossC
        ##########################################################################################
        self.DWX = 1 if CROSX(kline.close, self.lossC, self.REFC, self.REFlossC) == 1 and self.Num <= 10 else 0
        self.UPX = 1 if CROSX(self.lossC, kline.close, self.REFlossC, self.REFC) == 1 and self.Num <= 10 else 0
        ##########################################################################################
        self.DWCOND, self.UPCOND = DWCOND, UPCOND
        self.REFC, self.REFMH, self.REFML, self.REFOC = kline.close, self.MH, self.ML, self.OC


class Cond001A:
    '''
    板块顶底专属版
    '''

    def __init__(self):
        self.SMA = SMA()
        self.Hlists, self.Llists, self.H, self.L, self.MH, self.ML = [], [], 0, 0, 0, 0
        self.OC, self.REFOC, self.MAa, self.MAb, self.MAc, self.MAd = 0, 0, MA(), MA(), MA(), MA()
        self.REFC, self.REFMH, self.REFML = 0, 0, 0
        self.DWCOND, self.UPCOND = 0, 0
        self.UP, self.DW = 0, 0
        self.UPX, self.DWX = 0, 0
        self.lossC, self.REFlossC = 0, 0
        self.Num = 0

    def input(self, kline, Harge=20, Large=20):
        ##########################################################################################
        self.Num += 1
        self.SMA.input(kline.close, 21, 3)
        self.Hlists.append(kline.high)
        self.Llists.append(kline.low)
        if len(self.Hlists) > 10:
            self.H, self.L = max(self.Hlists[-(Harge + 1):-1]), min(self.Llists[-(Large + 1):-1])
        else:
            self.H, self.L = 0, 0
        MK = abs(self.H - self.L) * 0.2
        self.MH, self.ML = self.H - MK, self.L + MK
        ##########################################################################################
        self.OC = abs(kline.close - kline.open)
        self.MAa.input(self.OC, 20)
        self.MAb.input(self.MAa.ma, 100)
        ##########################################################################################
        self.MAc.input(kline.close, 5)
        self.MAd.input(self.OC, 60)
        ##########################################################################################
        # 底部上穿
        DWCOND = CROSX(kline.close + self.MAb.ma * 0.5, self.ML, self.REFC,
                       self.REFML) == 1 and kline.close > kline.open
        DWCOND1 = DWCOND or self.DWCOND
        DWCOND2 = (self.OC + self.REFOC) >= 2 * self.MAd.ma
        DWCOND3 = (self.OC + self.REFOC) > self.MAb.ma
        DWCOND4 = (kline.close + self.MAb.ma * 0.5) > self.MAc.ma
        ##########################################################################################
        # 顶部下穿
        UPCOND = CROSX(self.MH, kline.close - self.MAb.ma * 0.5, self.REFMH,
                       self.REFC) == 1 and kline.close < kline.open
        UPCOND1 = UPCOND or self.UPCOND
        UPCOND2 = (self.OC + self.REFOC) >= 2 * self.MAd.ma
        UPCOND3 = (self.OC + self.REFOC) > self.MAb.ma
        UPCOND4 = (kline.close - self.MAb.ma * 0.5) < self.MAc.ma
        ##########################################################################################
        self.UP = 1 if UPCOND and UPCOND1 and UPCOND2 and UPCOND3 and UPCOND4 else 0
        self.DW = 1 if DWCOND and DWCOND1 and DWCOND2 and DWCOND3 and DWCOND4 else 0

        if self.UP:
            self.lossC = kline.high
            self.Num = 1
        if self.DW:
            self.lossC = kline.low
            self.Num = 1
        self.REFlossC = self.lossC
        ##########################################################################################
        self.DWX = 1 if CROSX(kline.close, self.lossC, self.REFC, self.REFlossC) == 1 and self.Num <= 10 else 0
        self.UPX = 1 if CROSX(self.lossC, kline.close, self.REFlossC, self.REFC) == 1 and self.Num <= 10 else 0
        ##########################################################################################
        self.DWCOND, self.UPCOND = DWCOND, UPCOND
        self.REFC, self.REFMH, self.REFML, self.REFOC = kline.close, self.MH, self.ML, self.OC


class Cond002A:
    '''板块顶底专属版'''

    def __init__(self):
        self.SMA = SMA()
        self.ATRBS = ATR()
        self.UP = 0
        self.DW = 0
        self.REFC = 0
        self.REFSMA = 0

    def input(self, kline):
        self.SMA.input(kline.open, 21, 3)
        self.ATRBS.input(kline)
        self.DW = 1 if CROSX(kline.close + self.ATRBS.atr * 0.5, self.SMA.sma, self.REFC,
                             self.REFSMA) == 1 else 0  # 底部上穿
        self.UP = 1 if CROSX(self.SMA.sma, kline.close - self.ATRBS.atr * 0.5, self.REFSMA,
                             self.REFC) == 1 else 0  # 顶部下穿
        ##########################################################################################
        self.REFC, self.REFSMA = kline.close, self.SMA.sma


class Cond002:
    def __init__(self):
        self.SMA = SMA()
        self.ATRBS = ATR()
        self.UP = 0
        self.DW = 0
        self.REFC = 0
        self.REFSMA = 0

    def input(self, kline):
        self.SMA.input(kline.open, 21, 3)
        self.ATRBS.input(kline)
        self.DW = 1 if CROSX(kline.close, self.SMA.sma, self.REFC, self.REFSMA) == 1 else 0  # and self.ATRBS.UP==1
        self.UP = 1 if CROSX(self.SMA.sma, kline.close, self.REFSMA, self.REFC) == 1 else 0  # and self.ATRBS.DW==1
        ##########################################################################################
        self.REFC, self.REFSMA = kline.close, self.SMA.sma
        # print(kline.trade_time,"UP",self.UP,"DW",self.DW)


# %% 顶底条件003
class RunDCond003:
    '''板块顶底专属板'''

    def __init__(self):
        self.Cond001 = Cond001A()
        self.Cond002 = Cond002A()
        self.OC, self.REFOC, self.num = 0, 0, 0
        self.rst = CLS({"sequence": [], "UPDW": [], "trade_time": [], "COCD": False, "mark": []})

    def run1D(self, DayKline, ablesearch=3):
        self.num += 1
        self.OC = abs(DayKline.close - DayKline.open)
        COCD = self.OC > 1.5 * self.REFOC
        self.rst.COCD = COCD
        self.Cond001.input(DayKline)
        self.Cond002.input(DayKline)
        ###%%条件判断#########################################
        trade_time = datetime.fromtimestamp(DayKline.time).strftime("%Y-%m-%d %H:%M:%S")
        if self.Cond001.UP or self.Cond001.UPX or self.Cond002.UP:
            self.rst.trade_time.append(trade_time)
            self.rst.sequence.append(self.num)
            self.rst.UPDW.append(1)  # 顶部
            if self.Cond001.UP:
                self.rst.mark.append("DD")
            elif self.Cond002.UP:
                self.rst.mark.append("ATR")
            else:
                self.rst.mark.append("XX")
        elif self.Cond001.DW or self.Cond001.DWX or self.Cond002.DW:
            self.rst.trade_time.append(trade_time)
            self.rst.sequence.append(self.num)
            self.rst.UPDW.append(-1)  # 底部
            if self.Cond001.DW:
                self.rst.mark.append("DD")
            elif self.Cond002.DW:
                self.rst.mark.append("ATR")
            else:
                self.rst.mark.append("XX")
        else:
            self.rst.sequence.append(self.num)
            if len(self.rst.UPDW) > 0: self.rst.UPDW.append(
                self.rst.UPDW[-1] + (self.rst.UPDW[-1] / abs(self.rst.UPDW[-1])))  # 空白
        #######################################################################    
        self.rst.trade_time = self.rst.trade_time[-ablesearch:]
        self.rst.sequence = self.rst.sequence[-ablesearch:]
        self.rst.UPDW = self.rst.UPDW[-ablesearch:]
        self.rst.mark = self.rst.mark[-ablesearch:]
        #######################################################################
        self.REFOC = self.OC


# %% 顶底条件002
class RunDCond002:
    def __init__(self):
        self.Cond001 = Cond001()
        self.Cond002 = Cond002()
        self.OC, self.REFOC, self.num = 0, 0, 0
        self.rst = CLS({"sequence": [], "UPDW": [], "trade_time": [], "COCD": False, "mark": []})

    def run1D(self, DayKline, ablesearch=3):
        self.num += 1
        self.OC = abs(DayKline.close - DayKline.open)
        COCD = self.OC > 1.5 * self.REFOC
        self.rst.COCD = COCD
        self.Cond001.input(DayKline)
        self.Cond002.input(DayKline)
        ###%%条件判断#########################################
        trade_time = datetime.fromtimestamp(DayKline.time).strftime("%Y-%m-%d %H:%M:%S")
        if self.Cond001.UP or self.Cond001.UPX or self.Cond002.UP:
            self.rst.trade_time.append(trade_time)
            self.rst.sequence.append(self.num)
            self.rst.UPDW.append(1)  # 顶部
            if self.Cond001.UP:
                self.rst.mark.append("DD")
            elif self.Cond002.UP:
                self.rst.mark.append("ATR")
            else:
                self.rst.mark.append("XX")
        elif self.Cond001.DW or self.Cond001.DWX or self.Cond002.DW:
            self.rst.trade_time.append(trade_time)
            self.rst.sequence.append(self.num)
            self.rst.UPDW.append(-1)  # 底部
            if self.Cond001.DW:
                self.rst.mark.append("DD")
            elif self.Cond002.DW:
                self.rst.mark.append("ATR")
            else:
                self.rst.mark.append("XX")
        else:
            # self.rst.trade_time.append(trade_time)
            self.rst.sequence.append(self.num)
            if len(self.rst.UPDW) > 0: self.rst.UPDW.append(
                self.rst.UPDW[-1] + (self.rst.UPDW[-1] / abs(self.rst.UPDW[-1])))  # 空白
            # self.rst.mark.append("")
        #######################################################################    
        self.rst.trade_time = self.rst.trade_time[-ablesearch:]
        self.rst.sequence = self.rst.sequence[-ablesearch:]
        self.rst.UPDW = self.rst.UPDW[-ablesearch:]
        self.rst.mark = self.rst.mark[-ablesearch:]
        #######################################################################
        self.REFOC = self.OC


# %% 顶底信号001
class RunDCond001:
    def __init__(self):
        self.Cond001 = Cond001()
        self.Cond002 = Cond002()
        self.OC, self.REFOC = 0, 0
        self.rst = CLS({"sequence": [], "UPDW": [], "trade_time": [], "COCD": False, "mark": []})

    def GetData(self, code, period="1d", s_time="2022-06-16 13:33:00", e_time="2023-12-16 21:33:00", limit=100):
        klines = io_klines.get_kline_data(code=code, period=period, s_time=s_time, e_time=e_time, limit=limit)
        return klines

    def run1D(self, code, period="1d", s_time="2022-06-16 13:33:00", e_time="2023-12-16 21:33:00", limit=100):
        klines = self.GetData(code=code, period=period, s_time=s_time, e_time=e_time, limit=limit)
        self.OC, self.REFOC = 0, 0
        for i in range(len(klines)):
            self.OC = abs(klines[i].close - klines[i].open)
            COCD = self.OC > 1.5 * self.REFOC
            self.rst.COCD = COCD
            self.Cond001.input(klines[i])
            self.Cond002.input(klines[i])
            if len(klines) - i < 4:
                if self.Cond001.UP or self.Cond001.UPX or self.Cond002.UP:
                    self.rst.trade_time.append(klines[i].trade_time)
                    self.rst.sequence.append(len(klines) - i)
                    self.rst.UPDW.append(1)  # 顶部
                    if self.Cond001.UP:
                        self.rst.mark.append("DD")
                    elif self.Cond002.UP:
                        self.rst.mark.append("ATR")
                    else:
                        self.rst.mark.append("XX")
                elif self.Cond001.DW or self.Cond001.DWX or self.Cond002.DW:
                    self.rst.trade_time.append(klines[i].trade_time)
                    self.rst.sequence.append(len(klines) - i)
                    self.rst.UPDW.append(-1)  # 底部
                    if self.Cond001.DW:
                        self.rst.mark.append("DD")
                    elif self.Cond002.DW:
                        self.rst.mark.append("ATR")
                    else:
                        self.rst.mark.append("XX")
                else:
                    self.rst.trade_time.append(klines[i].trade_time)
                    self.rst.sequence.append(len(klines) - i)
                    self.rst.UPDW.append(0)  # 空白
                    self.rst.mark.append("")
                ################################################################################################    
                self.rst.trade_time = self.rst.trade_time[-3:]
                self.rst.sequence = self.rst.sequence[-3:]
                self.rst.UPDW = self.rst.UPDW[-3:]
                self.rst.mark = self.rst.mark[-3:]

                ################################################################################################
            else:
                self.rst = CLS({"sequence": [], "UPDW": [], "trade_time": [], "COCD": False, "mark": []})
            self.REFOC = self.OC
            # print(self.rst.sequence,self.rst.UPDW,self.rst.trade_time)


class UPDWLS:
    '''
    浮盈加仓：
    计算开仓一手成本，每当盈利金额达到可开一手成本的N倍，就在5日线或整数（50|100）位置加仓N手。
    亏损减仓：
    计算开仓一手成本，每当亏损金额达到可开一手成本的N倍，就在开盘价位置减仓N手。
    注：加仓，减仓每日限定触发一次。
    '''

    def __init__(self):
        self.acc = ACCGet()
        self.ADDNum, self.LossNum = 0, 0

    def NewLS(self, Contract, Price, ls, PositionLS, PositionPrice):
        '''
        #合约,价格,手数,持仓手数,持仓成本
        '''
        ###计算交易盈亏
        direction = ls / abs(ls) if ls != 0 else 0
        fee = CLS(self.acc.GetFee(Contract, "moni186"))  # 手续费
        OneCost = Price * fee.Multiplier * fee.MarginRatioByMoney  # type: ignore # 一手成本
        if OneCost <= 0: return 0  # 成本小于等于零返回空
        EarnNum = (Price - PositionPrice) * (PositionLS * fee.Multiplier)  # type: ignore #交易盈亏
        ADDBASE = (abs(EarnNum) // OneCost)  # 加减仓数量
        if ADDBASE > 0:  # 需要加减仓
            ###判断盈利加仓或亏损减仓
            if EarnNum > 0:
                self.ADDNum += 1  # 记录加减仓次数,需在策略端每日重置为0
                logger.debug(f"\n||加仓数量||{ADDBASE}||{Contract}||持仓:{PositionLS}||预期:{ls}")
                return ls + direction * ADDBASE  # 返回最终仓位数量
            elif EarnNum < 0:
                self.LossNum += 1  # 记录加减仓次数,需在策略端每日重置为0
                if abs(ADDBASE) > abs(ls): ADDBASE = abs(ls)
                logger.debug(f"\n||减仓数量||{ADDBASE}||{Contract}||持仓:{PositionLS}||预期:{ls}")
                return ls - direction * ADDBASE  # 返回最终仓位数量
        else:
            return ls
        ########################################################################


def ThreadPools(max_workers, Fun, arges):
    '''线程池带参数'''
    with ThreadPoolExecutor(max_workers) as executor: executor.map(Fun, arges)


class KLine():
    def __init__(self, time=0, Open=0, high=0, low=0, close=0, volume=0, oi=0, instrument=""):
        self.time, self.open, self.close, self.high, self.low, self.volume, self.oi, self.instrument = \
            time, Open, close, high, low, volume, oi, instrument  # 合约号instrument

    def appendkline(self, kline):  # 合并k线，首条k线需要把time清零
        if self.time == 0:
            self.__dict__.update(kline.__dict__)
        elif self.time < kline.time:
            self.time = kline.time
            if self.high < kline.high: self.high = kline.high
            if self.low > kline.low: self.low = kline.low
            self.close = kline.close
            self.volume += kline.volume


class NAZF:  #
    '''有效波动'''

    def __init__(self, mark=0):
        self.mark = mark
        self.NaZF, self.Var, self.Border, self.BorderMA20 = 0, 0, 0, 0
        self.NaZFlist, self.VARlist, self.BorderN, self.MAC5, self.MA20 = [], [], [], MA(), MA()
        self.ReNaZF, self.REFH, self.REFL, self.REFC = 1, 1, 1, 1

    def input(self, kline, promotion=1, e0=5, e20=20, N=25):
        self.NaZFlist.append(self.ReNaZF)
        self.NaZFlist = self.NaZFlist[-e0:]
        self.MAC5.input(kline.close, e0, promotion)
        self.NaZF = ((self.REFH / kline.low - 1) + (kline.high / (self.REFL - abs(kline.close - self.REFC)))) / 2 \
            if (self.REFL - abs(kline.close - self.REFC)) else 0
        self.VARlist.append(kline.close - self.MAC5.ma)
        self.VARlist = self.VARlist[-e0:]
        self.Var = abs(sum(self.VARlist) / len(self.VARlist)) if len(self.VARlist) != 0 else 0
        self.Border = (max(self.NaZFlist)) * self.Var
        self.BorderN.append(self.Border)
        self.BorderN = self.BorderN[-N:]
        self.MA20.input(self.Border, e20, promotion)
        self.BorderMA20 = self.MA20.ma
        self.ReNaZF, self.REFH, self.REFL, self.REFC = self.NaZF, kline.high, kline.low, kline.close


class Boll:
    '''BOLL'''

    def __init__(self, mark=0):
        self.mark = mark
        self.MA = MA()
        self.alist, self.BSP, self.side = [], 0, 0  # side:0表示HP=LP,side:1为HP>LP,-1为HP<LP
        self.boll, self.bollH, self.bollL, self.stdvalue = 0, 0, 0, 0

    def input(self, price, N=26, G=2, promotion=1):
        self.MA.input(price, N, promotion)
        self.boll = self.MA.ma
        self.alist.append(price)
        self.alist = self.alist[-promotion * N:]
        self.stdvalue = G * (np.std(np.array(self.alist)))
        self.bollH = self.boll + self.stdvalue  # BOLL上轨
        self.bollL = self.boll - self.stdvalue  # BOLL下轨
        HP, LP = abs(price - self.bollH), abs(price - self.bollL)
        self.BSP = max(HP, LP) / min(HP, LP) if min(HP, LP) != 0 else 0  # BS空间比值
        if HP > LP:
            self.side = 1
        elif HP < LP:
            self.side = -1
        else:
            self.side = 0

    def replace(self, price, N=26, G=2, promotion=1):
        self.MA.replace(price)
        self.boll = self.MA.ma
        self.alist[-1] = price
        self.stdvalue = G * (np.std(np.array(self.alist)))
        self.bollH = self.boll + self.stdvalue  # BOLL上轨
        self.bollL = self.boll - self.stdvalue  # BOLL下轨
        HP, LP = abs(price - self.bollH), abs(price - self.bollL)
        self.BSP = max(HP, LP) / min(HP, LP) if min(HP, LP) != 0 else 0  # BS空间比值
        if HP > LP:
            self.side = 1
        elif HP < LP:
            self.side = -1
        else:
            self.side = 0


class HLV:  # 定周期(默认5日)最高价最低价
    def __init__(self, mark=0):
        self.mark = mark
        self.H, self.L, self.argelist = 0, 0, []

    def input(self, price, arge=5):
        self.argelist.append(price)
        self.argelist = self.argelist[-2 * arge:]
        self.H = max(self.argelist[-arge:])
        self.L = min(self.argelist[-arge:])


class EMA:
    def __init__(self, mark=0):
        self.mark = mark
        self.ema = 0

    def input(self, price, e0=12, promotion=1):
        self.ema = (price * 2 + self.ema * (promotion * e0 - 1)) / (promotion * e0 + 1)


# //////////////////////////////////////////////////////////////////////////////
class BIAS:
    '''
    姜总摸顶底止盈
    BIAS5:=ABS((CLOSE-MA(CLOSE,5))/MA(CLOSE,5)*100);
    BSAE:=MA(BIAS5,120)*1.5;
    CX10:=CROSS(C,MA(C,5)) OR CROSS(MA(C,5),C);
    CONDBIAS:=CROSS(BIAS5,BSAE) ;
    
    COND10DAY:=SUMBARS(CX10=1,1)-1;
    STICKLINE(CONDBIAS AND COND10DAY>=5,H,L,3,0),COLORYELLOW;
    '''

    def __init__(self, mark=0):
        self.bias = 0
        self.MA = MA()
        self.Mbias = MA()
        self.MA5 = MA()
        self.REFbias, self.REFMbias = 0, 0
        self.REFC, self.REFMA5 = 0, 0
        self.NumXMA, self.Cls = 0, 0

    def input(self, price, e0=5, promotion=1, em=120):
        self.NumXMA += 1
        self.MA5.input(price)
        self.MA.input(price, e0, promotion)
        self.bias = 100 * (price - self.MA.ma) / self.MA.ma if self.MA.ma != 0 else 0
        self.Mbias.input(self.bias, em, promotion)
        CONDICTION = ((CROSX(price, self.MA5.ma, self.REFC, self.REFMA5) == 1) or (
                CROSX(self.MA5.ma, price, self.REFMA5, self.REFC) == 1))
        CDLS0 = CROSX(self.bias, self.Mbias.ma, self.REFbias, self.REFMbias) == 1
        if CONDICTION: self.NumXMA = 0
        self.Cls = 1 if self.NumXMA >= 3 and CDLS0 else 0  #
        self.REFbias, self.REFMbias = self.bias, self.Mbias.ma
        self.REFC, self.REFMA5 = price, self.MA5.ma
    # //////////////////////////////////////////////////////////////////////////////


class MABS:
    def __init__(self, Num=720, mark=0):
        self.mark, self.Num = mark, Num
        self.HREF, self.LREF, self.CREF, self.OREF = 1, 1, 1, 1
        self.Datas, self.BSDK, self.EMABS = [[1, ] * 4], 0, EMA()

    def input(self, kline, e0=12, promotion=1):
        price, self.promotion = kline.close, promotion
        self.EMABS.input(price, e0, self.promotion)
        UDS = 1 if (price >= self.EMABS.ema) else 0
        if (UDS == 0) & (self.LREF > kline.low):
            self.BSDK = -2  # 线下创新低|顺跌
        if (UDS == 1) & (self.HREF < kline.high):
            self.BSDK = 2  # 线上创新高|顺涨
        if (UDS == 1) & (self.LREF > kline.low):
            self.BSDK = 1  # 线上创新低|逆涨
        if (UDS == 0) & (self.HREF < kline.high):
            self.BSDK = -1  # 线下创新高|逆跌
        if (self.LREF > kline.low) & (self.HREF < kline.high):
            self.BSDK = 3  # 即创新高又创新低|发散K
        if (self.LREF <= kline.low) & (self.HREF >= kline.high):
            self.BSDK = -3  # 即不创新高又不创新低|收敛K
        self.HREF, self.LREF, self.CREF, self.OREF = kline.high, kline.low, kline.close, kline.open
        self.Datas.append([kline.high, kline.low, kline.close, kline.open])
        self.Datas = self.Datas[-self.Num:]


class MACD:
    def __init__(self, mark=0):
        self.mark = mark
        self.RE12, self.RE26 = 0, 0
        self.REFmacd, self.REFdea, self.REFdif = 0, 0, 0
        self.REF2macd, self.REF2dea, self.REF2dif = 0, 0, 0
        self.macd, self.dea, self.dif, self.EMA26, self.EMA12, self.EMA09 = 0, 0, 0, EMA(), EMA(), EMA()

    def input(self, price, promotion=1, e12=12, e26=26, e9=9, sl=False, prd=1):
        '''  
        price价格 promotion扩张倍数 e12=12, e26=26, e9=9,sl长线使用MA,prd状态切换阈值
        '''
        self.REF2macd, self.REF2dea, self.REF2dif = self.REFmacd, self.REFdea, self.REFdif
        self.REFmacd, self.REFdea, self.REFdif = self.macd, self.dea, self.dif
        self.EMA26.input(price, e26, promotion)
        self.EMA12.input(price, e12, promotion)
        self.dif = (self.EMA12.ema - self.EMA26.ema)
        self.EMA09.input(self.dif, e9, promotion)
        self.dea = self.EMA09.ema
        self.macd = (self.dif - self.dea) * 2


# //////////////////////////////////////////////////////////////////////////////
class ATR:  # ATR 默认为14， 已与通达信的相一致 note by luhx 2023-6-22
    '''
    SML:SMA(O,21,3),COLORGREEN,LINETHICK2;
    STICKLINE((CROSS(C,HPG)AND CROSS(C,SML) )OR ( CROSS(C,LPG)AND CROSS(C,SML) ),H,L,3,0),COLORYELLOW;
    STICKLINE((CROSS(HPG,C)AND CROSS(SML,C)) OR ( CROSS(LPG,C)AND CROSS(SML,C) ),H,L,3,0),COLORBLUE;
    '''

    def __init__(self, mark=0):
        self.mark = mark
        self.MTR, self.HBS, self.LBS, self.atr, self.Matr, self.REFHL = 0, 0, 0, 0, MA(), [1, ] * 4
        self.HH, self.LL = HLV(), HLV()
        self.SMA = SMA()
        self.UP, self.DW = 0, 0
        self.REFC, self.REFSMA, self.REFHBS, self.REFLBS = 0, 0, 0, 0

    def input(self, kline, promotion=1, M0=14, arge=21, N=2):  # 回撤N个ATR离场
        self.MTR = max(kline.high - kline.low, abs(self.REFHL[2] - kline.high), abs(self.REFHL[2] - kline.low))
        self.Matr.input(self.MTR, M0, promotion)  # ATR  MA的M0平均
        self.HH.input(kline.high, promotion * arge)  # arge周期的最高
        self.LL.input(kline.low, promotion * arge)  # arge周期的最低
        self.atr = self.Matr.ma  # 波动率
        self.REFHL = [kline.high, kline.low, kline.close, kline.open]
        self.HBS = self.HH.H - N * self.atr
        self.LBS = self.LL.L + N * self.atr
        self.SMA.input(kline.open)
        ###########################################################################
        CROSXSMAC = CROSX(kline.close, self.SMA.sma, self.REFC, self.REFSMA) == 1
        CROSXCSMA = CROSX(self.SMA.sma, kline.close, self.REFSMA, self.REFC) == 1
        CROSXHBSC = CROSX(kline.close, self.HBS, self.REFC, self.REFHBS) == 1
        CROSXCHBS = CROSX(self.HBS, kline.close, self.REFHBS, self.REFC) == 1
        CROSXLBSC = CROSX(kline.close, self.LBS, self.REFC, self.REFLBS) == 1
        CROSXCLBS = CROSX(self.LBS, kline.close, self.REFLBS, self.REFC) == 1
        ###########################################################################
        self.UP = 1 if (CROSXHBSC and CROSXSMAC) or (CROSXLBSC and CROSXSMAC) else 0
        self.DW = 1 if (CROSXCHBS and CROSXCSMA) or (CROSXCLBS and CROSXCSMA) else 0
        ###########################################################################
        self.REFC, self.REFSMA, self.REFHBS, self.REFLBS = kline.close, self.SMA.sma, self.HBS, self.LBS


class RSI:
    def __init__(self, mark=0):
        self.mark = mark
        self.SMA0, self.SMA1, self.rsi, self.REFC = SMA(), SMA(), 0, 1

    def input(self, price, N=6, M=1, promotion=1):
        self.SMA0.input(max(price - self.REFC, 0), N, M, promotion)
        self.SMA1.input(abs(price - self.REFC), N, M, promotion)
        self.rsi = (self.SMA0.sma / self.SMA1.sma * 100) if self.SMA1.sma else 0
        self.REFC = price


class SMA:
    def __init__(self, mark=0):
        self.mark = mark
        self.sma = 0

    def input(self, price, N=21, M=3, promotion=1):
        self.sma = (price * M + self.sma * (N * promotion - M)) / (N * promotion) if (N * promotion) != 0 else 0


class Recursion:
    '''K线重采样'''

    def __init__(self, kline):
        self.datek = 0
        self.open = kline.open
        self.time = kline.time
        self.close = kline.close
        self.high = kline.high
        self.low = kline.low
        self.volume = kline.volume

    def Newkline(self, kline):
        self.datek += 1
        self.open = kline.open
        self.time = kline.time
        self.close = kline.close
        self.high = kline.high
        self.low = kline.low
        self.volume = kline.volume

    def Append(self, kline):
        self.time = kline.time
        if self.high < kline.high: self.high = kline.high
        if self.low > kline.low: self.low = kline.low
        self.close = kline.close
        self.volume += kline.volume


class AUMA:
    '''自由均线'''

    def __init__(self, Length=2000, mark=0):
        self.mark, self.Length = mark, Length
        self.ma, self.malist = 0, []

    def input(self, price, e0=5, promotion=1):
        if e0 < 1: e0 = 1
        self.malist.append(price)
        self.ma = sum(self.malist[-promotion * e0:]) / len(self.malist[-promotion * e0:])
        self.malist = self.malist[-self.Length:]


class MAFS:
    """日内自由均线"""

    def __init__(self, Length=2000, mark=0):
        self.AUMAA = AUMA(Length=Length, mark=0)
        self.AUMAB = AUMA(Length=Length, mark=1)
        self.AUMAC = AUMA(Length=Length, mark=2)

    def input(self, price, N=1, ks1day=345, limitN=5):
        halfD = int(0.5 * ks1day)
        lastN = ks1day - N
        halfN = abs(halfD - N)
        lastH = halfD - halfN
        N0 = min(lastH, halfN)
        N1 = max(lastH, halfN)
        N2 = max(lastN, N)
        if N0 < limitN: N0 = limitN
        self.AUMAA.input(price, N0, 1)
        self.AUMAB.input(price, N1, 1)
        self.AUMAC.input(price, N2, 1)


class MA:
    def __init__(self, mark=0):
        self.mark = mark
        self.ma, self.malist = 0, []

    def input(self, price, e0=5, promotion=1):
        if e0 < 1: e0 = 1
        self.malist.append(price)
        self.malist = self.malist[-promotion * e0:]
        self.ma = sum(self.malist) / len(self.malist)

    def replace(self, price):
        self.malist[-1] = price
        self.ma = sum(self.malist) / len(self.malist)


class D_MA:
    """动态MA,通常用于先计算前4日平均线，然后在第5日不断用分时数据替换进去，计算5天实时MA"""

    def __init__(self, e0=5):
        self.e0 = e0
        self.ma = 0
        self.MA = MA()

    def input(self, price):
        self.MA.input(price, self.e0)
        self.ma = self.MA.ma

    def input_s(self, price):
        if len(self.MA.malist) < self.e0:
            self.input(price)
        else:
            self.MA.replace(price)
            self.ma = self.MA.ma


class CCL:
    '''
    持仓量
    '''

    def __init__(self, mark=0):
        self.mark = mark
        self.REFC, self.REFOI, self.result = 1, 1, ''

    def input(self, price, oi):
        self.OI = oi
        self.oiX = self.OI - self.REFOI
        self.result = '空头增仓' if (self.oiX > 0 and price < self.REFC) else self.result
        self.result = '多头增仓' if (self.oiX > 0 and price >= self.REFC) else self.result
        self.result = '多头减仓' if (self.oiX < 0 and price < self.REFC) else self.result
        self.result = '空头减仓' if (self.oiX < 0 and price >= self.REFC) else self.result
        self.REFC, self.REFOI = price, oi


class KLitem:
    def __init__(self):
        self.ti = 0
        self.ls = 0
        self.gap = 0
        self.ks1day = 1
        self.lastktime = 0
        self.lastprice = 0
        self.initprice = 1
        self.opentimes = [0, 0, 0, 0]
        self.closetimes = [0, 0, 0, 0]

    def init_s(self, df):
        days = []
        for i in range(len(df)):
            if (2 * 3600 + 15 * 60) < df["time"].iloc[i] % (86400) < (2 * 3600 + 30 * 60):
                self.gap = 1  # 上午没有休盘
                self.closetimes[2] = 0  # 上午只有1段, 1 1 3 0
                self.opentimes[2] = 0  # 上午只有1段, 1 1 3 0
            if i == 0:
                pass
            else:
                t0 = self.Segs(df["time"].iloc[i - 1])
                t1 = self.Segs(df["time"].iloc[i]) if i + 1 < len(df) else 0
                if t0 != t1:
                    self.closetimes[t0] = df["time"].iloc[i - 1] % (86400)
                    self.opentimes[t1] = df["time"].iloc[i] % (86400)
                    if t0 == 3:
                        days.append(i - self.ti)  # 上一个k先在结算前, 下一k先在结算后
                        self.ks1day = max(days)
                        self.ti = i
            if len(days) > 3: break
        return self.__dict__

    def Segs(self, t):  # 0:夜盘 1:上午盘1 2:上午盘2 3:下午盘
        sec = t % 86400
        if sec < 8400:
            return 1  # 上午10:20 (10-8)*3600+20*60=8400
        elif sec < 18000:
            return int(2 - self.gap)  # 中午 13:00 (13-8)*3600=18000
        elif sec < 36000:
            return 3  # 下午 18:00 (18-8)*3600=36000
        else:
            return 0  # 夜盘


class TickCCL:
    '''
    Tick级持仓量
    '''

    def __init__(self, mark=0):
        self.mark = mark
        self.REFAsk, self.REFBid, self.REFC, self.REFOI, self.result = 1, 1, 1, 1, ''

    def input(self, price, ask_price, bid_price, oi):
        self.OI = oi
        self.oiX = self.OI - self.REFOI
        price_up = price >= self.REFAsk or price >= ask_price
        price_down = price < self.REFBid or price < bid_price

        self.result = '空头增仓' if (self.oiX > 0 and price_down) else self.result
        self.result = '多头增仓' if (self.oiX > 0 and price_up) else self.result
        self.result = '多头减仓' if (self.oiX < 0 and price_down) else self.result
        self.result = '空头减仓' if (self.oiX < 0 and price_up) else self.result
        self.REFAsk, self.REFBid, self.REFC, self.REFOI = ask_price, bid_price, price, oi


class CCLX:
    '''
    持仓净值
    OIX:=OPI-REF(OPI,1);
    CX:=C-REF(C,1);
    UP:=IF(CX>0 AND OIX>0 , OIX,0);//多开
    DW:=IF(CX<0 AND OIX>0 ,-OIX,0);//空开
    UPX:=IF(CX<0 AND OIX<=0 ,OIX,0);//多平
    DWX:=IF(CX>0 AND OIX<=0 ,-OIX,0);//空平
    净买:SUM(UP+UPX,30);//持仓量净买
    净卖:SUM(DW+DWX,30);//持仓量净卖
    净买卖:SUM(UP+UPX+DW+DWX,30);////多空持仓量净值
    '''

    def __init__(self, mark=0):
        self.mark = mark
        self.REFC, self.REFOI = 1, 1
        self.NoB, self.NoS, self.NoBS = MA(), MA(), MA()

    def input(self, kline, Bnum=30, Snum=30, BSnum=30, promotion=1):
        CX = kline.close - self.REFC
        OIX = kline.oi - self.REFOI
        UP = OIX if CX > 0 and OIX > 0 else 0  ###多开
        DW = -OIX if CX < 0 and OIX > 0 else 0  ###空开
        UPX = OIX if CX < 0 and OIX <= 0 else 0  ###多平
        DWX = -OIX if CX > 0 and OIX <= 0 else 0  ###空平
        self.NoB.input(UP + UPX, Bnum, promotion)
        self.NoS.input(DW + DWX, Snum, promotion)
        self.NoBS.input(UP + UPX + DW + DWX, BSnum, promotion)
        self.REFC, self.REFOI = kline.close, kline.oi


# //////////////////////////////////////////////////////////////////////////////
def GETKEY(JSData):
    '''获取Json的KEY'''
    try:
        return list(JSData.keys())
    except:
        return []


def Float(strs):
    '''转换成浮点数'''
    try:
        return float(strs)
    except:
        return strs


def JsonSTF(JSData):
    for key in GETKEY(JSData): JSData[key] = Float(JSData[key])
    return JSData


def EVALI(STData):
    for i in ["[", "]", "{", "}"]:
        if i in STData: STData = eval(STData)
    return STData


def delint(strold):  #
    '''字符串去数字'''
    import re
    return re.sub(r'[0-9]+', '', strold)


def SendTxT(content, to_wxid="wxid_r4ufgqjx57x822", ipport="l6kbw2sh3.neiwangyun.net"):
    import requests, json
    url = f"http://{ipport}/api/wechat/send_text/"
    data = {
        "to_wxid": to_wxid,  # "发送对象wxid"
        "content": content  # "消息内容 "
    }
    headers = {'content-type': 'application/json;charset=utf-8'}
    return requests.post(url, json.dumps(data), headers=headers)


class DifferB:
    def __init__(self, mark=0):
        self.mark = mark
        self.REFP, self.REFD, self.difB, self.REFF, self.difdif = None, None, 0, None, 0

    def input(self, price, per):
        '''
        计算X增量百分比的增量的倍数
        XV:=100*(V-REF(V,1))/V;
        YV:ABS(XV-REF(XV,1)),STICK;
        IF(YV/REF(YV,1)>6,YV,0),VOLSTICK;
        '''
        if not self.REFP: self.REFP = price
        difper = 100 * (price - self.REFP) / self.REFP
        if not self.REFD: self.REFD = difper
        self.difdif = abs(difper - self.REFD) / self.REFD
        if not self.REFF: self.REFF = self.difdif
        self.difB = self.difdif if (self.difdif / self.REFF) > per else 0
        self.REFP, self.REFD, self.REFF = price, difper, self.difdif


def ts2str(ts=0):
    '''
    ts 时间戳 转时间
    默认0 返回当前时间
    '''
    if ts == 0:
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')


def RoundUp(number, digit):  # 四舍五入
    '''向上取舍'''
    import math
    return math.ceil(number * (10 ** digit)) / (10 ** digit)


def RoundDw(number, digit):  # 向下取整digit精度
    '''向下取舍'''
    import math
    return math.floor(number * (10 ** digit)) / (10 ** digit)


def strTotime(strtime, Format='%Y-%m-%d %H:%M:%S'):
    '''
    时间转换:
    日期转时间戳  
    '''
    if type(strtime) == str:
        strtime = time.mktime(time.strptime(strtime, Format))
    else:
        strtime = time.mktime(strtime.timetuple())
    return strtime


class KLitem:
    def __init__(self):
        self.ti = 0
        self.ls = 0
        self.gap = 0
        self.ks1day = 1
        self.lastktime = 0
        self.lastprice = 0
        self.initprice = 1
        self.opentimes = [0, 0, 0, 0]
        self.closetimes = [0, 0, 0, 0]

    def init_s(self, df):
        days = []
        for i in range(len(df)):
            if (2 * 3600 + 15 * 60) < df["time"].iloc[i] % (86400) < (2 * 3600 + 30 * 60):
                self.gap = 1  # 上午没有休盘
                self.closetimes[2] = 0  # 上午只有1段, 1 1 3 0
                self.opentimes[2] = 0  # 上午只有1段, 1 1 3 0
            if i == 0:
                pass
            else:
                t0 = self.Segs(df["time"].iloc[i - 1])
                t1 = self.Segs(df["time"].iloc[i]) if i + 1 < len(df) else 0
                if t0 != t1:
                    self.closetimes[t0] = df["time"].iloc[i - 1] % (86400)
                    self.opentimes[t1] = df["time"].iloc[i] % (86400)
                    if t0 == 3:
                        days.append(i - self.ti)  # 上一个k先在结算前, 下一k先在结算后
                        self.ks1day = max(days)
                        self.ti = i
            if len(days) > 3: break
        return self.__dict__

    def Segs(self, t):  # 0:夜盘 1:上午盘1 2:上午盘2 3:下午盘
        sec = t % 86400
        if sec < 8400:
            return 1  # 上午10:20 (10-8)*3600+20*60=8400
        elif sec < 18000:
            return int(2 - self.gap)  # 中午 13:00 (13-8)*3600=18000
        elif sec < 36000:
            return 3  # 下午 18:00 (18-8)*3600=36000
        else:
            return 0  # 夜盘


def ToDaytime():
    '''返回今日零点时间戳'''
    from datetime import date
    return int(time.mktime(date.today().timetuple()))


class CLS:
    '''将JSON转化成属性'''

    def __init__(self, json):
        try:
            if type(json) == dict:
                self.__dict__.update(json)
            else:
                self.__dict__.update(json.__dict__)
        except Exception as exception:
            user_log.error(str(exception))


def ToCLS(jsdata):
    '''深度JSON==>>CLS'''
    if type(jsdata) == dict:
        for key in GETKEY(jsdata):
            jsdata[key] = ToCLS(jsdata[key])
        return CLS(jsdata)
    elif type(jsdata) == list:
        return [ToCLS(dats) for dats in jsdata]
    else:
        return jsdata


def CROSX(a, b, ra, rb):  #
    '''金叉死叉判断'''
    if (a > b) & (ra <= rb): return 1  # 金叉
    if (a < b) & (ra >= rb): return -1  # 死叉
    if (a > b) & (ra > rb): return 2  #
    if (a < b) & (ra < rb):
        return -2  #
    else:
        return 0


import time, json, websocket
from apscheduler.schedulers.background import BackgroundScheduler


class ClientWS:
    def __init__(self, name, ClientID):
        '''
        name:"test"
        ClientID:123
        '''
        self.num = 0
        self.mesgID = 0
        self.name = name  # 客户端名称
        self.ClientID = ClientID  # 客户端编号
        self.queue = queue.Queue()

    def BkGdSRun(self, fun, args=[]):
        '''后台调度运行'''
        scheduler = BackgroundScheduler()
        scheduler.add_job(fun, args=args)
        scheduler.start()
        ###后台程序启动
        user_log.info(f"\n||客户端:{self.name}程序后台启动||")

    def on_open(self, ws):
        """
        可发送握手信息,订阅信息
        """
        ws.send(json.dumps({"account_id": "668800280333", "sub_content": "position", "subscribe": "on"}))

    def on_data(self, ws, data, datatype, continue_flag):
        """
        服务端返回数据
        """
        pass
        # user_log.info(f"{data}, {datatype}, {continue_flag}")

    def on_message(self, ws, message):
        """
        服务端返回消息
        """
        self.queue.put(json.loads(message))
        user_log.info(message)

    def on_error(self, ws, error):
        """
        服务端返回的错误信息
        """
        user_log.info(error)

    def on_close(self, ws, close_status_code, close_msg):
        """
        服务端返回连接关闭信息
        """
        user_log.error(f"{close_status_code} : {close_msg}")

    def on_ping(self, ws, frame_data):
        '''服务端发送ping时客户端自动调用该函数'''
        # websocket.ABNF.OPCODE_PING,websocket.ABNF.OPCODE_PONG
        # user_log.info(f"\nping_data: {frame_data}")# 接收到PING数据帧后, 需要立即给服务端回复 PONG 数据帧
        ws.send(round(time.time() * 1000).to_bytes(13, 'big'), websocket.ABNF.OPCODE_PONG)

    def on_pong(self, ws, frame_data):
        '''服务端发送pong时客户端自动调用该函数'''
        # user_log.info(f"\npong_data: {frame_data}")
        if not frame_data: ws.send(round(time.time() * 1000).to_bytes(13, 'big'), websocket.ABNF.OPCODE_PING)

    def ClientApp(self, link):
        '''客户端连接'''
        self.ws = websocket.WebSocketApp(
            link,
            on_open=self.on_open,
            on_message=self.on_message,
            on_data=self.on_data,
            on_error=self.on_error,
            on_ping=self.on_ping,
            on_pong=self.on_pong,
            on_close=self.on_close)
        self.ws.run_forever(ping_interval=110, ping_timeout=None)

    def startClient(self, link):
        '''多线程启动多个链接'''
        self.ClientApp(link)
        while True:
            print("断线重连喔.......")
            self.ClientApp(link)
            time.sleep(3)


def PositionSlip(wsdata):
    position = ToCLS(wsdata)
    ablehold = {}
    if not position.hold_list: return None
    for data in position.hold_list:
        if data.Position > 0:
            code = data.InstrumentID
            if code not in GETKEY(ablehold):
                ablehold[code] = {"InstrumentID": code, "up": 0, "dw": 0}
            if data.PosiDirection == "2":
                ablehold[code]["up"] = data.Position
            if data.PosiDirection == "3":
                ablehold[code]["dw"] = -1 * data.Position
    return {ablehold[x]["InstrumentID"]: (ablehold[x]["up"] + ablehold[x]["dw"]) for x in ablehold}


# %% 推送信号
def SendBS(MHTP: int = 1, OCBS: str = "0", LSBS: str = "3", price: float = 0, VOL: int = 1, ACCOUNT="moni186",
           ip="192.168.1.186", CODE='FG209'):
    '''
    MHTP:#0涨跌停价,1市价成交
    OCBS:#0:开仓   1:平仓
    LSBS:#3:空头  2:多头
    '''
    url = "http://%s:8084/api/virtual/orderbuysell" % (ip)
    data = {
        'account_id': ACCOUNT,
        'account_type': 'virtual',
        'admin_id': '10000',
        'instrument_id': CODE,
        'effect_type': OCBS,  # 开平;0:开仓    1:平仓
        'posi_direction': LSBS,  # 多空3:空头2多头
        'match_type': MHTP,  # 0涨跌停价,1现价成交
        'price': price,
        'token': 'token',
        'user': 'user',
        'volume': VOL
    }
    user_log.info(f"发送买卖信号{data}")
    headers = {'content-type': 'application/json;charset=utf-8'}
    r = requests.post(url, json.dumps(data), headers=headers)
    system_log.info(r"\n||交易记录|%s" % json.dumps(data))
    system_log.info(r"\n||交易反馈|%s" % r.text)
    return r.text  # r=SendBS('moni186','FG209',1,"0","3",2,1)


def ToDaytime():
    '''返回今日零点时间戳'''
    from datetime import date
    return int(time.mktime(date.today().timetuple()))


def Account(account):
    '''
    获取指定账户信息
    注:保存的数据是str 转回只需eval()
    '''
    headers = {'Content-Type': 'application/json;charset=UTF-8'}
    url = Acconts_all()[account]['url'] + "/oneaccountinfo?investor_id=" + account
    r = requests.request('GET', url, headers=headers)
    result = r.json()["result"]
    result['CodesUP'] = [x.upper() for x in result['Codes']]
    return result


# //////////////////////////////////////////////////////////////////////////////
###数据库中取账户信息
class ACCGet:
    def __init__(self):
        ''' 
        初始化数据并更新到数据库REDIS
        '''
        self.account = Acconts_all()
        # self.ADDCFG("account",self.account)
        self.keys = list(self.account.keys())
        self.fees: Dict[str, Any] = {}

    def init_one_fee(self, contract, monixxx="668800280333"):
        if contract in self.fees:
            return
        if monixxx not in self.keys:
            monixxx = "668800280333"
        url, investor_id = self.account[monixxx]["url"], self.account[monixxx]["investor_id"]
        headers = {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'}
        url = "%s%s?investor_id=%s&code=%s" % (url, '/accountcode', investor_id, contract)
        for i in range(3):
            try:
                r = requests.request('GET', url, headers=headers)
            except Exception as e:
                logger.error(f"try [{i + 1}/3] times.  {url}: {str(e)}")
                time.sleep(1)
                continue
            try:
                if "result" in r.json():
                    self.fees[contract] = r.json()["result"]
                    logger.info(f"init fee ok: 【{contract}】: 合约乘数: {self.fees[contract]['Multiplier']}")
                    return
            except Exception as e:
                logger.info(str(e))
                logger.error(traceback.format_exc())
                return

    def GetACC(self, key):
        if key not in self.keys: key = "moni186"
        data, Fee = Account(key), {}
        for i in range(len(data["Codes"])):
            Fee[data["Codes"][i]] = self.GetFee(data["Contracts"][i], key)
        data["Fee"] = Fee
        self.ADDCFG(key, data)  # type: ignore
        return data

    # def ADDCFG(self,section,jsons,PATH="./ConFigs/AccData/"):
    #     CFG=ConFigDB(PATH)
    #     CFG.Add_section(section,jsons)
    #     CFG.Save(filename=section)
    # def RedACC(self,filename=None,PATH="./ConFigs/AccData/"):
    #     if filename not in self.keys:filename="moni186"
    #     CFG=ConFigDB(PATH)
    #     CFG.Read(filename)
    #     acc=CFG.GetDict(filename)
    #     #acc={Data[i][0]:EVALI(Data[i][1]) for i in range(len(Data))}
    #     return acc
    def GetFee(self, Contract, monixxx):
        self.init_one_fee(Contract, monixxx)
        if Contract in self.fees:
            return self.fees[Contract]
        if monixxx not in self.keys: monixxx = "668800280333"
        url, investor_id = self.account[monixxx]["url"], self.account[monixxx]["investor_id"]
        headers = {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'}
        url = "%s%s?investor_id=%s&code=%s" % (url, '/accountcode', investor_id, Contract)
        r = requests.request('GET', url, headers=headers)
        return r.json()["result"]


    def CreateTbl(self, tblname, DDL={}, Mkey=[], usedbname=None, markupdate=False):
        '''
        创建表
        DDL={}表结构
        Mkey=[]主键key
        INT
        CHAR(8) NOT NULL   定长字符串
        varchar(12) NOT NULL 变长字符串
        decimal(18,3) DEFAULT '0.000' 精准小数
        float(64,6) DEFAULT 0 7位有效数
        Double  DEFAULT NULL  15位有效数
        DATE YYYY-MM-DD	日期
        TIME	HH:MM:SS	时间
        YEAR	YYYY	年份
        DATETIME	YYYY-MM-DD HH:MM:SS	日期和时间
        TIMESTAMP	10位或13位整数（秒数）	时间戳
        "update_time":" datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
        
        '''
        if usedbname != None: self.UseDB(usedbname)
        if markupdate: DDL["update_time"] = "datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
        ###--------------------------------------------------------------------
        DDLkey = GETKEY(DDL)
        strddl = ""
        for i in range(len(DDLkey)):
            addstr = "" if i < 1 else ","
            strddl += f"{addstr}`{DDLkey[i]}` {DDL[DDLkey[i]]} "
        PMKEY = f",PRIMARY KEY ({self.Tostr(Mkey)})" if len(Mkey) > 0 else ""
        DDLstr = f"{strddl}{PMKEY}"
        user_log.info(DDLstr)
        ###--------------------------------------------------------------------
        return self.RunSql(f"create table {tblname}({DDLstr});")

    def AddField(self, tblname="test123", Fieldname="test", charset="CHAR(8) NOT NULL", usedbname=None):
        '''增加字段'''
        try:
            if usedbname != None: self.UseDB(usedbname)
            return self.RunSql(f"alter table {tblname} add {Fieldname} {charset};")
        except Exception as exception:
            user_log.error(str(exception))
            return str(exception)

    def DropField(self, tblname="test123", Fieldname="", usedbname=None):
        '''删除字段'''
        try:
            if usedbname != None: self.UseDB(usedbname)
            return self.RunSql(f"alter table {tblname} drop {Fieldname};")
        except Exception as exception:
            user_log.error(str(exception))
            return str(exception)

    def ModifyField(self, tblname="test123", Fieldname="", usedbname=None, charset="CHAR(8) NOT NULL"):
        '''修改字段属性'''
        try:
            if usedbname != None: self.UseDB(usedbname)
            return self.RunSql(f"alter table {tblname} modify {Fieldname} {charset};")
        except Exception as exception:
            user_log.error(str(exception))
            return str(exception)

    def Replace(self, tblname="test123", jsdata=[{}], method="replace into"):
        '''
        #insert into
        UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
        '''
        sqls = []
        try:
            for js in jsdata:
                k, v = GETKEY(js), list(js.values())
                ks, vs = self.Tostr(k), self.Tostr(v, False)
                strdata = f"{method} {tblname}({ks})values({vs});"
                user_log.info(f"sql:\n{strdata}")
                sqls.append(strdata)
            user_log.info("sql语句生成完成")
            return sqls
        except Exception as exception:
            user_log.error(str(exception))
            return sqls

    def Tostr(self, strs, nd=True, NaN=False):
        '''
        nd=True==>>  `A`,`B`,`C`
        nd=False==>>  'A','B','C'
        '''
        if type(strs) == str or (not hasattr(strs, '__iter__')): return strs
        sx = ""
        for i in range(len(strs)):
            s = "," if i > 0 else ""
            spt = '' if NaN else ("`" if nd else "\'")  # 子元素什么都不加
            sx += f"{s}{spt}{self.Tostr(strs[i], nd=False, NaN=True)}{spt}"
        return sx

    def GetData(self, string, rstdf=True):
        '''
        获取数据
        返回dataframe形式：rstdf=True
        返回columns和values :rstdf=False
        '''
        try:
            self.RunSql(string)
            data = self.Fetchall()
            columns = [field[0] for field in self.cursor.description]  #
            if rstdf:
                return pd.DataFrame(data, columns=columns)
            else:
                return {"columns": columns, "data": data}
        except Exception as exception:
            user_log.error(str(exception))
            if rstdf:
                return pd.DataFrame()
            else:
                {"columns": None, "data": None}

    def SaveDF(self, df, tblname="test123", method="replace into"):
        '''
        保存df replace into 或者 insert into
        '''
        jsdata = EVALI(df.to_json(orient="records", force_ascii=False))
        kss, vss = [], []
        for js in jsdata:
            k, v = GETKEY(js), list(js.values())
            ks, vs = self.Tostr(k), self.Tostr(v, False)
            kss.append(ks), vss.append(vs)
        columns, value = kss[0], ""
        for i in range(len(vss)):
            s = "," if i > 0 else ""
            value += f"{s}({vss[i]})"
        strdata = f"{method} {tblname}({columns})values{value};"
        return strdata
